--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Node / ReticulumProjects / MeshChatX.git / files / meshchatx / src / backend / database / __init__.py
Displaying Raw • Download
meshchatx/src/backend/database/__init__.py 9cd0e66d8aa6dac478b6001f703ca6777f8f709c (9cd0e66d) Text, 24.31 KB
T8b949e# SPDX-License-Identifier: 0BSD
Tff7b72import T7ee787json
Tff7b72import T7ee787logging
Tff7b72import T7ee787os
Tff7b72import T7ee787re
Tff7b72import T7ee787shutil
Tff7b72import T7ee787zipfile
Tff7b72from T7ee787datetime Tff7b72import Te6edf3UTCTb4b4b4, Te6edf3datetime
Tff7b72from T7ee787.T7ee787access_attempts Tff7b72import Te6edf3AccessAttemptsDAO
Tff7b72from T7ee787.T7ee787announces Tff7b72import Te6edf3AnnounceDAO
Tff7b72from T7ee787.T7ee787config Tff7b72import Te6edf3ConfigDAO
Tff7b72from T7ee787.T7ee787contacts Tff7b72import Te6edf3ContactsDAO
Tff7b72from T7ee787.T7ee787crash_history Tff7b72import Te6edf3CrashHistoryDAO
Tff7b72from T7ee787.T7ee787debug_logs Tff7b72import Te6edf3DebugLogsDAO
Tff7b72from T7ee787.T7ee787gifs Tff7b72import Te6edf3UserGifsDAO
Tff7b72from T7ee787.T7ee787map_drawings Tff7b72import Te6edf3MapDrawingsDAO
Tff7b72from T7ee787.T7ee787messages Tff7b72import Te6edf3MessageDAO
Tff7b72from T7ee787.T7ee787misc Tff7b72import Te6edf3MiscDAO
Tff7b72from T7ee787.T7ee787provider Tff7b72import Te6edf3DatabaseProvider
Tff7b72from T7ee787.T7ee787ringtones Tff7b72import Te6edf3RingtoneDAO
Tff7b72from T7ee787.T7ee787schema Tff7b72import Te6edf3DatabaseSchema
Tff7b72from T7ee787.T7ee787sticker_packs Tff7b72import Te6edf3UserStickerPacksDAO
Tff7b72from T7ee787.T7ee787stickers Tff7b72import Te6edf3UserStickersDAO
Tff7b72from T7ee787.T7ee787telemetry Tff7b72import Te6edf3TelemetryDAO
Tff7b72from T7ee787.T7ee787telephone Tff7b72import Te6edf3TelephoneDAO
Tff7b72from T7ee787.T7ee787voicemails Tff7b72import Te6edf3VoicemailDAO
Te6edf3BACKUP_BASELINE_FILENAME Tff7b72= Ta5d6ff"Ta5d6ffbackup-baseline.jsonTa5d6ff"
Te6edf3BACKUP_MANIFEST_NAME Tff7b72= Ta5d6ff"Ta5d6ffbackup-manifest.jsonTa5d6ff"
Te6edf3BACKUP_SKIP_DIR_NAMES Tff7b72= Tffa657frozensetTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6ffdatabase-backupsTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffsnapshotsTa5d6ff"Tb4b4b4}Tb4b4b4)
Te6edf3MIN_SIZE_RATIO Tff7b72= T79c0ff0.2
Te6edf3_log Tff7b72= Te6edf3loggingTff7b72.Td2a8ffgetLoggerTb4b4b4(Ta5d6ff"Ta5d6ffmeshchatx.databaseTa5d6ff"Tb4b4b4)
Tff7b72class T56d364DatabaseRestoreErrorTb4b4b4(Tf85149RuntimeErrorTb4b4b4)Tb4b4b4:
Tff7b72pass
Te6edf3_PRAGMA_READ_NAME_RE Tff7b72= Te6edf3reTff7b72.Td2a8ffcompileTb4b4b4(Ta5d6ffrTa5d6ff"Ta5d6ff^[A-Za-z_][A-Za-z0-9_]*Ta5d6ff\Ta5d6ffZTa5d6ff"Tb4b4b4)
Te6edf3_ALLOWED_WAL_CHECKPOINT_MODES Tff7b72= Tffa657frozensetTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6ffPASSIVETa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffFULLTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffRESTARTTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTRUNCATETa5d6ff"Tb4b4b4}Tb4b4b4)
Tff7b72def Td2a8ff_sanitize_pragma_read_nameTb4b4b4(Te6edf3nameTb4b4b4: Tffa657str Tff7b72| Tff7b72NoneTb4b4b4) Tff7b72-Tff7b72> Tffa657str Tff7b72| Tff7b72NoneTb4b4b4:
T8b949e"""Allow only simple SQLite pragma tokens for dynamic ``PRAGMA name`` reads."""
Tff7b72if Tff7b72not Te6edf3name Tff7b72or Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3nameTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Te6edf3token Tff7b72= Te6edf3nameTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3token Tff7b72or Tff7b72not Te6edf3_PRAGMA_READ_NAME_RETff7b72.Td2a8ffmatchTb4b4b4(Te6edf3tokenTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72return Te6edf3token
Tff7b72def Td2a8ff_sanitize_wal_checkpoint_modeTb4b4b4(Te6edf3modeTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657strTb4b4b4:
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3modeTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffWAL checkpoint mode must be a stringTa5d6ff"
Tff7b72raise Tf85149ValueErrorTb4b4b4(Te6edf3msgTb4b4b4)
Te6edf3m Tff7b72= Te6edf3modeTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8ffupperTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3m Tff7b72not Tff7b72in Te6edf3_ALLOWED_WAL_CHECKPOINT_MODESTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffInvalid WAL checkpoint mode: Tffd700{Te6edf3modeTffd700!r}Ta5d6ff"
Tff7b72raise Tf85149ValueErrorTb4b4b4(Te6edf3msgTb4b4b4)
Tff7b72return Te6edf3m
Tff7b72class T56d364DatabaseTb4b4b4:
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3db_pathTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffprovider Tff7b72= Te6edf3DatabaseProviderTff7b72.Td2a8ffget_instanceTb4b4b4(Te6edf3db_pathTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffschema Tff7b72= Te6edf3DatabaseSchemaTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffconfig Tff7b72= Te6edf3ConfigDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffmessages Tff7b72= Te6edf3MessageDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffannounces Tff7b72= Te6edf3AnnounceDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffmisc Tff7b72= Te6edf3MiscDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8fftelephone Tff7b72= Te6edf3TelephoneDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8fftelemetry Tff7b72= Te6edf3TelemetryDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffvoicemails Tff7b72= Te6edf3VoicemailDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffringtones Tff7b72= Te6edf3RingtoneDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffcontacts Tff7b72= Te6edf3ContactsDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffmap_drawings Tff7b72= Te6edf3MapDrawingsDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffstickers Tff7b72= Te6edf3UserStickersDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffsticker_packs Tff7b72= Te6edf3UserStickerPacksDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffgifs Tff7b72= Te6edf3UserGifsDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffdebug_logs Tff7b72= Te6edf3DebugLogsDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffaccess_attempts Tff7b72= Te6edf3AccessAttemptsDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffcrash_history Tff7b72= Te6edf3CrashHistoryDAOTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTb4b4b4)
Tff7b72def Td2a8ffinitializeTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_tune_sqlite_pragmasTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffschemaTff7b72.Td2a8ffinitializeTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ffexecute_sqlTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3queryTb4b4b4, Te6edf3paramsTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3paramsTb4b4b4)
Tff7b72def Td2a8ff_tune_sqlite_pragmasTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffPRAGMA journal_mode=WALTa5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffPRAGMA synchronous=NORMALTa5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffPRAGMA wal_autocheckpoint=1000Ta5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffPRAGMA temp_store=MEMORYTa5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffPRAGMA cache_size=-8000Ta5d6ff"Tb4b4b4) T8b949e# 8 MB
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffPRAGMA mmap_size=67108864Ta5d6ff"Tb4b4b4) T8b949e# 64 MB
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffPRAGMA busy_timeout=5000Ta5d6ff"Tb4b4b4) T8b949e# 5 s wait on lock contention
Tff7b72except Tf85149Exception Tff7b72as Te6edf3excTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffSQLite pragma setup failed: Tffd700{Te6edf3excTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ff_get_pragma_valueTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3pragmaTb4b4b4: Tffa657strTb4b4b4, Te6edf3defaultTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
Te6edf3safe Tff7b72= Te6edf3_sanitize_pragma_read_nameTb4b4b4(Te6edf3pragmaTb4b4b4)
Tff7b72if Te6edf3safe Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Te6edf3default
Tff7b72tryTb4b4b4:
Te6edf3cursor Tff7b72= Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffPRAGMA Tffd700{Te6edf3safeTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3row Tff7b72= Te6edf3cursorTff7b72.Td2a8fffetchoneTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3row Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Te6edf3default
Tff7b72return Te6edf3rowTb4b4b4[T79c0ff0Tb4b4b4]
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72return Te6edf3default
Tff7b72def Td2a8ff_get_database_file_statsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72def Td2a8ffsize_forTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3pathTb4b4b4)
Tff7b72except Tf85149OSErrorTb4b4b4:
Tff7b72return T79c0ff0
Te6edf3db_path Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffdb_path
Te6edf3wal_path Tff7b72= Ta5d6fffTa5d6ff"Tffd700{Te6edf3db_pathTffd700}Ta5d6ff-walTa5d6ff"
Te6edf3shm_path Tff7b72= Ta5d6fffTa5d6ff"Tffd700{Te6edf3db_pathTffd700}Ta5d6ff-shmTa5d6ff"
Te6edf3main_bytes Tff7b72= Te6edf3size_forTb4b4b4(Te6edf3db_pathTb4b4b4)
Te6edf3wal_bytes Tff7b72= Te6edf3size_forTb4b4b4(Te6edf3wal_pathTb4b4b4)
Te6edf3shm_bytes Tff7b72= Te6edf3size_forTb4b4b4(Te6edf3shm_pathTb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffmain_bytesTa5d6ff"Tb4b4b4: Te6edf3main_bytesTb4b4b4,
Ta5d6ff"Ta5d6ffwal_bytesTa5d6ff"Tb4b4b4: Te6edf3wal_bytesTb4b4b4,
Ta5d6ff"Ta5d6ffshm_bytesTa5d6ff"Tb4b4b4: Te6edf3shm_bytesTb4b4b4,
Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4: Te6edf3main_bytes Tff7b72+ Te6edf3wal_bytes Tff7b72+ Te6edf3shm_bytesTb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ff_get_backup_baseline_pathTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4)Tb4b4b4:
Te6edf3default_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Ta5d6ff"Ta5d6ffdatabase-backupsTa5d6ff"Tb4b4b4)
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3default_dirTb4b4b4, Te6edf3BACKUP_BASELINE_FILENAMETb4b4b4)
Tff7b72def Td2a8ff_read_backup_baselineTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4)Tb4b4b4:
Te6edf3path Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_backup_baseline_pathTb4b4b4(Te6edf3storage_pathTb4b4b4)
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72tryTb4b4b4:
Tff7b72with Tffa657openTb4b4b4(Te6edf3pathTb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Tff7b72return Te6edf3jsonTff7b72.Td2a8ffloadTb4b4b4(Te6edf3fTb4b4b4)
Tff7b72except Tb4b4b4(Tf85149OSErrorTb4b4b4, Te6edf3jsonTff7b72.Td2a8ffJSONDecodeErrorTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72def Td2a8ff_write_backup_baselineTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4, Te6edf3message_countTb4b4b4, Te6edf3total_bytesTb4b4b4)Tb4b4b4:
Te6edf3path Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_backup_baseline_pathTb4b4b4(Te6edf3storage_pathTb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Te6edf3data Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4: Te6edf3message_countTb4b4b4,
Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4: Te6edf3total_bytesTb4b4b4,
Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4: Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}
Tff7b72with Tffa657openTb4b4b4(Te6edf3pathTb4b4b4, Ta5d6ff"Ta5d6ffwTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3jsonTff7b72.Td2a8ffdumpTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3fTb4b4b4, Te6edf3indentTff7b72=T79c0ff2Tb4b4b4)
Tff7b72except Tf85149OSError Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to write backup baseline: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ff_get_current_db_content_statsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3stats Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_database_file_statsTb4b4b4(Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3message_count Tff7b72= Tff7b72selfTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffcount_lxmf_messagesTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Te6edf3message_count Tff7b72= Tff7b72-T79c0ff1
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4: Te6edf3message_countTb4b4b4,
Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4: Te6edf3statsTb4b4b4[Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ff_is_backup_suspiciousTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3current_statsTb4b4b4, Te6edf3baselineTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3baselineTb4b4b4:
Tff7b72return Tff7b72False
Te6edf3prev_count Tff7b72= Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3prev_bytes Tff7b72= Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3curr_count Tff7b72= Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3curr_bytes Tff7b72= Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Tff7b72if Te6edf3prev_count Tff7b72> T79c0ff0 Tff7b72and Te6edf3curr_count Tff7b72== T79c0ff0Tb4b4b4:
Tff7b72return Tff7b72True
Tff7b72if Te6edf3prev_bytes Tff7b72> T79c0ff100_000 Tff7b72and Te6edf3curr_bytes Tff7b72< Te6edf3prev_bytes Tff7b72* Te6edf3MIN_SIZE_RATIOTb4b4b4:
Tff7b72return Tff7b72True
Tff7b72return Tff7b72False
Tff7b72def Td2a8ffcheck_db_health_at_openTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4)Tb4b4b4:
T8b949e"""Run integrity and baseline checks after opening the database.
Returns human-readable issue strings; empty if healthy.
"""
Te6edf3issues Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72tryTb4b4b4:
Te6edf3integrity_rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffintegrity_checkTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3integrity_rowsTb4b4b4:
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffDatabase integrity check failed: no resultTa5d6ff"Tb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB open health check: no resultTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3first Tff7b72= Te6edf3integrity_rowsTb4b4b4[T79c0ff0Tb4b4b4]
Te6edf3val Tff7b72= Tb4b4b4(
Tffa657nextTb4b4b4(Tffa657iterTb4b4b4(Te6edf3firstTff7b72.Td2a8ffvaluesTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4) Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3firstTb4b4b4, Tffa657dictTb4b4b4) Tff7b72else Te6edf3firstTb4b4b4[T79c0ff0Tb4b4b4]
Tb4b4b4)
Tff7b72if Te6edf3val Tff7b72!= Ta5d6ff"Ta5d6ffokTa5d6ff"Tb4b4b4:
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffDatabase integrity check failed: Tffd700{Te6edf3valTffd700!s}Ta5d6ff"Tb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB open health check: Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3valTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffDatabase integrity check error: Tffd700{Te6edf3eTffd700!s}Ta5d6ff"
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Te6edf3msgTb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB open health check: Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3msgTb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3current Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_current_db_content_statsTb4b4b4(Tb4b4b4)
Te6edf3baseline Tff7b72= Tff7b72selfTff7b72.Td2a8ff_read_backup_baselineTb4b4b4(Te6edf3storage_pathTb4b4b4)
Tff7b72if Tff7b72selfTff7b72.Td2a8ff_is_backup_suspiciousTb4b4b4(Te6edf3currentTb4b4b4, Te6edf3baselineTb4b4b4)Tb4b4b4:
Te6edf3prev_c Tff7b72= Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3prev_b Tff7b72= Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3curr_c Tff7b72= Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3curr_b Tff7b72= Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)
Te6edf3msg Tff7b72= Tb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffDatabase content anomaly: was Tffd700{Te6edf3prev_cTffd700}Ta5d6ff messages / Tffd700{Te6edf3prev_bTffd700}Ta5d6ff bytes, Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ffnow Tffd700{Te6edf3curr_cTffd700}Ta5d6ff / Tffd700{Te6edf3curr_bTffd700}Ta5d6ff. Restore from backup if needed.Ta5d6ff"
Tb4b4b4)
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Te6edf3msgTb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB open health check: Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3msgTb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffinfoTb4b4b4(
Ta5d6ff"Ta5d6ffDB open health check ok: messages=Tffd700%sTa5d6ff total_bytes=Tffd700%sTa5d6ff"Tb4b4b4,
Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB open health check (baseline): Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3eTb4b4b4)
Tff7b72return Te6edf3issues
Tff7b72def Td2a8ffcheck_db_health_at_closeTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4)Tb4b4b4:
T8b949e"""Run health checks before closing the database (for logging only).
Returns issue strings; empty if healthy.
"""
Te6edf3issues Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72tryTb4b4b4:
Te6edf3integrity_rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffintegrity_checkTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3integrity_rowsTb4b4b4:
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffDatabase integrity check failed: no resultTa5d6ff"Tb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB close health check: no resultTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3first Tff7b72= Te6edf3integrity_rowsTb4b4b4[T79c0ff0Tb4b4b4]
Te6edf3val Tff7b72= Tb4b4b4(
Tffa657nextTb4b4b4(Tffa657iterTb4b4b4(Te6edf3firstTff7b72.Td2a8ffvaluesTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4) Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3firstTb4b4b4, Tffa657dictTb4b4b4) Tff7b72else Te6edf3firstTb4b4b4[T79c0ff0Tb4b4b4]
Tb4b4b4)
Tff7b72if Te6edf3val Tff7b72!= Ta5d6ff"Ta5d6ffokTa5d6ff"Tb4b4b4:
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffDatabase integrity check failed: Tffd700{Te6edf3valTffd700!s}Ta5d6ff"Tb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB close health check: integrity failedTa5d6ff"Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB close health check: Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3eTb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3current Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_current_db_content_statsTb4b4b4(Tb4b4b4)
Te6edf3baseline Tff7b72= Tff7b72selfTff7b72.Td2a8ff_read_backup_baselineTb4b4b4(Te6edf3storage_pathTb4b4b4)
Tff7b72if Tff7b72selfTff7b72.Td2a8ff_is_backup_suspiciousTb4b4b4(Te6edf3currentTb4b4b4, Te6edf3baselineTb4b4b4)Tb4b4b4:
Te6edf3issuesTff7b72.Td2a8ffappendTb4b4b4(
Ta5d6ff"Ta5d6ffDatabase content anomaly detected at close. Consider restoring from backup.Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB close health check: content anomalyTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffinfoTb4b4b4(
Ta5d6ff"Ta5d6ffDB close health check ok: messages=Tffd700%sTa5d6ff total_bytes=Tffd700%sTa5d6ff"Tb4b4b4,
Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3currentTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(Ta5d6ff"Ta5d6ffDB close health check (baseline): Tffd700%sTa5d6ff"Tb4b4b4, Te6edf3eTb4b4b4)
Tff7b72return Te6edf3issues
Tff7b72def Td2a8ff_database_pathsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3db_path Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffdb_path
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4: Te6edf3db_pathTb4b4b4,
Ta5d6ff"Ta5d6ffwalTa5d6ff"Tb4b4b4: Ta5d6fffTa5d6ff"Tffd700{Te6edf3db_pathTffd700}Ta5d6ff-walTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffshmTa5d6ff"Tb4b4b4: Ta5d6fffTa5d6ff"Tffd700{Te6edf3db_pathTffd700}Ta5d6ff-shmTa5d6ff"Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ffget_database_health_snapshotTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3page_size Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffpage_sizeTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4) Tff7b72or T79c0ff0
Te6edf3page_count Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffpage_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4) Tff7b72or T79c0ff0
Te6edf3freelist_pages Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6fffreelist_countTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4) Tff7b72or T79c0ff0
Te6edf3freelist_bytes Tff7b72= Tb4b4b4(
Te6edf3page_size Tff7b72* Te6edf3freelist_pages Tff7b72if Te6edf3page_size Tff7b72> T79c0ff0 Tff7b72and Te6edf3freelist_pages Tff7b72> T79c0ff0 Tff7b72else T79c0ff0
Tb4b4b4)
Tff7b72if Te6edf3freelist_bytes Tff7b72> T79c0ff0Tb4b4b4:
Te6edf3free_bytes Tff7b72= Te6edf3freelist_bytes
Tff7b72elseTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3db_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffdb_pathTb4b4b4)Tb4b4b4)
Te6edf3free_bytes Tff7b72= Te6edf3shutilTff7b72.Td2a8ffdisk_usageTb4b4b4(Te6edf3db_dirTb4b4b4)Tff7b72.Td2a8fffree Tff7b72if Te6edf3db_dir Tff7b72else T79c0ff0
Tff7b72except Tf85149OSErrorTb4b4b4:
Te6edf3free_bytes Tff7b72= T79c0ff0
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffquick_checkTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffquick_checkTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffjournal_modeTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffjournal_modeTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffsynchronousTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffsynchronousTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffwal_autocheckpointTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffwal_autocheckpointTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffauto_vacuumTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffauto_vacuumTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffpage_sizeTa5d6ff"Tb4b4b4: Te6edf3page_sizeTb4b4b4,
Ta5d6ff"Ta5d6ffpage_countTa5d6ff"Tb4b4b4: Te6edf3page_countTb4b4b4,
Ta5d6ff"Ta5d6fffreelist_pagesTa5d6ff"Tb4b4b4: Te6edf3freelist_pagesTb4b4b4,
Ta5d6ff"Ta5d6ffestimated_free_bytesTa5d6ff"Tb4b4b4: Te6edf3free_bytesTb4b4b4,
Ta5d6ff"Ta5d6fffilesTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_database_file_statsTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ff_checkpoint_walTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3modeTb4b4b4: Tffa657str Tff7b72= Ta5d6ff"Ta5d6ffTRUNCATETa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3safe_mode Tff7b72= Te6edf3_sanitize_wal_checkpoint_modeTb4b4b4(Te6edf3modeTb4b4b4)
Tff7b72return Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffPRAGMA wal_checkpoint(Tffd700{Te6edf3safe_modeTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8fffetchallTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ffrun_database_vacuumTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
T8b949e# Attempt to checkpoint WAL, ignore errors if busy
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_checkpoint_walTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffWarning: WAL checkpoint during vacuum failed (non-critical): Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffexecute_sqlTb4b4b4(Ta5d6ff"Ta5d6ffVACUUMTa5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_tune_sqlite_pragmasTb4b4b4(Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffhealthTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffget_database_health_snapshotTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
T8b949e# Wrap in a cleaner error message
Tff7b72raise Tf85149ExceptionTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffDatabase vacuum failed: Tffd700{Te6edf3eTffd700!s}Ta5d6ff"Tb4b4b4) Tff7b72from T7ee787e
Tff7b72def Td2a8ffrun_database_recoveryTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3actions Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3actionsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffstepTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffquick_check_beforeTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffresultTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffquick_checkTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Te6edf3actionsTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6ffstepTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffwal_checkpointTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffresultTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_checkpoint_walTb4b4b4(Tb4b4b4)Tb4b4b4}Tb4b4b4)
Te6edf3integrity_rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffintegrity_checkTb4b4b4(Tb4b4b4)
Te6edf3integrity Tff7b72= Tb4b4b4[Te6edf3rowTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72for Te6edf3row Tff7b72in Te6edf3integrity_rowsTb4b4b4] Tff7b72if Te6edf3integrity_rows Tff7b72else Tb4b4b4[Tb4b4b4]
Te6edf3actionsTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6ffstepTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffintegrity_checkTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffresultTa5d6ff"Tb4b4b4: Te6edf3integrityTb4b4b4}Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffvacuumTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_tune_sqlite_pragmasTb4b4b4(Tb4b4b4)
Te6edf3actionsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffstepTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffquick_check_afterTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffresultTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ff_get_pragma_valueTb4b4b4(Ta5d6ff"Ta5d6ffquick_checkTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffactionsTa5d6ff"Tb4b4b4: Te6edf3actionsTb4b4b4,
Ta5d6ff"Ta5d6ffhealthTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffget_database_health_snapshotTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ff_checkpoint_and_closeTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ff_checkpoint_walTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to checkpoint WAL: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffclose_allTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to close database: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffcloseTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffclose_allTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ffclose_allTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72if Tffa657hasattrTb4b4b4(Tff7b72selfTb4b4b4, Ta5d6ff"Ta5d6ffproviderTa5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffclose_allTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ff_identity_storage_dirTb4b4b4(Tff7b72selfTb4b4b4) Tff7b72-Tff7b72> Tffa657strTb4b4b4:
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffdb_pathTb4b4b4)Tb4b4b4)
Tff7b72def Td2a8ff_add_identity_storage_to_zipTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3zfTb4b4b4: Te6edf3zipfileTff7b72.Td2a8ffZipFileTb4b4b4,
Te6edf3db_basenamesTb4b4b4: Tffa657setTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4,
Tb4b4b4) Tff7b72-Tff7b72> Tffa657listTb4b4b4[Tffa657strTb4b4b4]Tb4b4b4:
Te6edf3identity_dir Tff7b72= Tff7b72selfTff7b72.Td2a8ff_identity_storage_dirTb4b4b4(Tb4b4b4)
Te6edf3includedTb4b4b4: Tffa657listTb4b4b4[Tffa657strTb4b4b4] Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3rootTb4b4b4, Te6edf3dirsTb4b4b4, Te6edf3files Tff7b72in Te6edf3osTff7b72.Td2a8ffwalkTb4b4b4(Te6edf3identity_dirTb4b4b4)Tb4b4b4:
Te6edf3dirsTb4b4b4[Tb4b4b4:Tb4b4b4] Tff7b72= Tb4b4b4[Te6edf3d Tff7b72for Te6edf3d Tff7b72in Te6edf3dirs Tff7b72if Te6edf3d Tff7b72not Tff7b72in Te6edf3BACKUP_SKIP_DIR_NAMESTb4b4b4]
Tff7b72for Te6edf3name Tff7b72in Te6edf3filesTb4b4b4:
Tff7b72if Te6edf3name Tff7b72in Te6edf3db_basenames Tff7b72or Te6edf3name Tff7b72== Te6edf3BACKUP_MANIFEST_NAMETb4b4b4:
Tff7b72continue
Te6edf3full_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3rootTb4b4b4, Te6edf3nameTb4b4b4)
Te6edf3rel_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffrelpathTb4b4b4(Te6edf3full_pathTb4b4b4, Te6edf3identity_dirTb4b4b4)Tff7b72.Td2a8ffreplaceTb4b4b4(Ta5d6ff"Tffea00\\Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff/Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3rel_pathTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ff..Ta5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72continue
Te6edf3zfTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3full_pathTb4b4b4, Te6edf3arcnameTff7b72=Te6edf3rel_pathTb4b4b4)
Te6edf3includedTff7b72.Td2a8ffappendTb4b4b4(Te6edf3rel_pathTb4b4b4)
Tff7b72return Te6edf3included
Tf0883e@staticmethod
Tff7b72def Td2a8ff_safe_zip_extract_memberTb4b4b4(
Te6edf3zfTb4b4b4: Te6edf3zipfileTff7b72.Td2a8ffZipFileTb4b4b4, Te6edf3memberTb4b4b4: Tffa657strTb4b4b4, Te6edf3target_dirTb4b4b4: Tffa657str
Tb4b4b4) Tff7b72-Tff7b72> Tff7b72NoneTb4b4b4:
Te6edf3dest_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3target_dirTb4b4b4, Te6edf3memberTb4b4b4)Tb4b4b4)
Te6edf3abs_target Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Te6edf3target_dirTb4b4b4)
Tff7b72if Tff7b72not Te6edf3dest_pathTff7b72.Td2a8ffstartswithTb4b4b4(Te6edf3abs_target Tff7b72+ Te6edf3osTff7b72.Td2a8ffsepTb4b4b4) Tff7b72and Te6edf3dest_path Tff7b72!= Te6edf3abs_targetTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffUnsafe zip entry path: Tffd700{Te6edf3memberTffd700}Ta5d6ff"
Tff7b72raise Te6edf3DatabaseRestoreErrorTb4b4b4(Te6edf3msgTb4b4b4)
Te6edf3zfTff7b72.Td2a8ffextractTb4b4b4(Te6edf3memberTb4b4b4, Te6edf3target_dirTb4b4b4)
Tff7b72def Td2a8ff_backup_to_zipTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3backup_pathTb4b4b4: Tffa657strTb4b4b4)Tb4b4b4:
Te6edf3paths Tff7b72= Tff7b72selfTff7b72.Td2a8ff_database_pathsTb4b4b4(Tb4b4b4)
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3backup_pathTb4b4b4)Tb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
T8b949e# ensure WAL is checkpointed to get a consistent snapshot
Tff7b72selfTff7b72.Td2a8ff_checkpoint_walTb4b4b4(Tb4b4b4)
Te6edf3main_filename Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffbasenameTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4]Tb4b4b4)
Te6edf3db_basenames Tff7b72= Tb4b4b4{Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffbasenameTb4b4b4(Te6edf3pTb4b4b4) Tff7b72for Te6edf3p Tff7b72in Te6edf3pathsTff7b72.Td2a8ffvaluesTb4b4b4(Tb4b4b4)Tb4b4b4}
Tff7b72with Te6edf3zipfileTff7b72.Td2a8ffZipFileTb4b4b4(Te6edf3backup_pathTb4b4b4, Ta5d6ff"Ta5d6ffwTa5d6ff"Tb4b4b4, Te6edf3compressionTff7b72=Te6edf3zipfileTff7b72.Td2a8ffZIP_DEFLATEDTb4b4b4) Tff7b72as Te6edf3zfTb4b4b4:
Te6edf3zfTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3arcnameTff7b72=Te6edf3main_filenameTb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffwalTa5d6ff"Tb4b4b4]Tb4b4b4)Tb4b4b4:
Te6edf3zfTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffwalTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3arcnameTff7b72=Ta5d6fffTa5d6ff"Tffd700{Te6edf3main_filenameTffd700}Ta5d6ff-walTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffshmTa5d6ff"Tb4b4b4]Tb4b4b4)Tb4b4b4:
Te6edf3zfTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffshmTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3arcnameTff7b72=Ta5d6fffTa5d6ff"Tffd700{Te6edf3main_filenameTffd700}Ta5d6ff-shmTa5d6ff"Tb4b4b4)
Te6edf3included Tff7b72= Tff7b72selfTff7b72.Td2a8ff_add_identity_storage_to_zipTb4b4b4(Te6edf3zfTb4b4b4, Te6edf3db_basenamesTb4b4b4)
Te6edf3manifest Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6ffversionTa5d6ff"Tb4b4b4: T79c0ff1Tb4b4b4,
Ta5d6ff"Ta5d6ffcreated_atTa5d6ff"Tb4b4b4: Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffincludes_identity_storageTa5d6ff"Tb4b4b4: Tff7b72TrueTb4b4b4,
Ta5d6ff"Ta5d6fffilesTa5d6ff"Tb4b4b4: Tffa657sortedTb4b4b4(Te6edf3includedTb4b4b4)Tb4b4b4,
Tb4b4b4}
Te6edf3zfTff7b72.Td2a8ffwritestrTb4b4b4(Te6edf3BACKUP_MANIFEST_NAMETb4b4b4, Te6edf3jsonTff7b72.Td2a8ffdumpsTb4b4b4(Te6edf3manifestTb4b4b4, Te6edf3indentTff7b72=T79c0ff2Tb4b4b4)Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffpathTa5d6ff"Tb4b4b4: Te6edf3backup_pathTb4b4b4,
Ta5d6ff"Ta5d6ffsizeTa5d6ff"Tb4b4b4: Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3backup_pathTb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffidentity_filesTa5d6ff"Tb4b4b4: Tffa657lenTb4b4b4(Te6edf3includedTb4b4b4)Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ffbackup_databaseTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3storage_pathTb4b4b4,
Te6edf3backup_pathTb4b4b4: Tffa657str Tff7b72| Tff7b72None Tff7b72= Tff7b72NoneTb4b4b4,
Te6edf3max_countTb4b4b4: Tffa657int Tff7b72| Tff7b72None Tff7b72= Tff7b72NoneTb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3default_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Ta5d6ff"Ta5d6ffdatabase-backupsTa5d6ff"Tb4b4b4)
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3default_dirTb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Te6edf3timestamp Tff7b72= Te6edf3datetimeTff7b72.Td2a8ffnowTb4b4b4(Te6edf3UTCTb4b4b4)Tff7b72.Td2a8ffstrftimeTb4b4b4(Ta5d6ff"Ta5d6ff%Ta5d6ffYTa5d6ff%Ta5d6ffmTffd700%dTa5d6ff-Ta5d6ff%Ta5d6ffHTa5d6ff%Ta5d6ffMTa5d6ff%Ta5d6ffSTa5d6ff"Tb4b4b4)
Te6edf3current_stats Tff7b72= Tff7b72selfTff7b72.Td2a8ff_get_current_db_content_statsTb4b4b4(Tb4b4b4)
Te6edf3baseline Tff7b72= Tff7b72selfTff7b72.Td2a8ff_read_backup_baselineTb4b4b4(Te6edf3storage_pathTb4b4b4)
Te6edf3suspicious Tff7b72= Tff7b72selfTff7b72.Td2a8ff_is_backup_suspiciousTb4b4b4(Te6edf3current_statsTb4b4b4, Te6edf3baselineTb4b4b4)
Tff7b72if Te6edf3backup_path Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72if Te6edf3suspiciousTb4b4b4:
Te6edf3backup_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(
Te6edf3default_dirTb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ffbackup-SUSPICIOUS-Tffd700{Te6edf3timestampTffd700}Ta5d6ff.zipTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3backup_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3default_dirTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffbackup-Tffd700{Te6edf3timestampTffd700}Ta5d6ff.zipTa5d6ff"Tb4b4b4)
Te6edf3result Tff7b72= Tff7b72selfTff7b72.Td2a8ff_backup_to_zipTb4b4b4(Te6edf3backup_pathTb4b4b4)
Tff7b72if Te6edf3suspiciousTb4b4b4:
Te6edf3_logTff7b72.Td2a8ffwarningTb4b4b4(
Ta5d6ff"Ta5d6ffBackup data-loss guard: current DB looks wrong (was Tffd700%sTa5d6ff messages / Tffd700%sTa5d6ff bytes, now Tffd700%sTa5d6ff / Tffd700%sTa5d6ff); Ta5d6ff"
Ta5d6ff"Ta5d6ffwrote backup-SUSPICIOUS-*.zip, skipping rotationTa5d6ff"Tb4b4b4,
Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tffa657printTb4b4b4(
Ta5d6ff"Ta5d6ffBackup data-loss guard: current database looks wrong Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ff(was Tffd700{Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6ffmessage_countTa5d6ff'Tb4b4b4)Tffd700}Ta5d6ff messages / Tffd700{Te6edf3baselineTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6fftotal_bytesTa5d6ff'Tb4b4b4)Tffd700}Ta5d6ff bytes, Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ffnow Tffd700{Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6ffmessage_countTa5d6ff'Tb4b4b4)Tffd700}Ta5d6ff / Tffd700{Te6edf3current_statsTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6fftotal_bytesTa5d6ff'Tb4b4b4)Tffd700}Ta5d6ff). Ta5d6ff"
Ta5d6ff"Ta5d6ffWrote backup-SUSPICIOUS-*.zip; skipping rotation and baseline update. Check disk and DB.Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3resultTb4b4b4[Ta5d6ff"Ta5d6ffsuspiciousTa5d6ff"Tb4b4b4] Tff7b72= Tff7b72True
Te6edf3resultTb4b4b4[Ta5d6ff"Ta5d6ffbaselineTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3baseline
Te6edf3resultTb4b4b4[Ta5d6ff"Ta5d6ffcurrent_statsTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3current_stats
Tff7b72return Te6edf3result
Tff7b72if Te6edf3max_count Tff7b72is Tff7b72not Tff7b72None Tff7b72and Te6edf3max_count Tff7b72> T79c0ff0Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3backups Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3file Tff7b72in Te6edf3osTff7b72.Td2a8fflistdirTb4b4b4(Te6edf3default_dirTb4b4b4)Tb4b4b4:
Tff7b72if Tb4b4b4(
Te6edf3fileTff7b72.Td2a8ffendswithTb4b4b4(Ta5d6ff"Ta5d6ff.zipTa5d6ff"Tb4b4b4)
Tff7b72and Te6edf3fileTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ffbackup-Ta5d6ff"Tb4b4b4)
Tff7b72and Ta5d6ff"Ta5d6ffSUSPICIOUSTa5d6ff" Tff7b72not Tff7b72in Te6edf3file
Tb4b4b4)Tb4b4b4:
Te6edf3full_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3default_dirTb4b4b4, Te6edf3fileTb4b4b4)
Te6edf3stats Tff7b72= Te6edf3osTff7b72.Td2a8ffstatTb4b4b4(Te6edf3full_pathTb4b4b4)
Te6edf3backupsTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Te6edf3full_pathTb4b4b4, Te6edf3statsTff7b72.Td2a8ffst_mtimeTb4b4b4)Tb4b4b4)
Tff7b72if Tffa657lenTb4b4b4(Te6edf3backupsTb4b4b4) Tff7b72> Te6edf3max_countTb4b4b4:
Te6edf3backupsTff7b72.Td2a8ffsortTb4b4b4(Te6edf3keyTff7b72=Tff7b72lambda Te6edf3xTb4b4b4: Te6edf3xTb4b4b4[T79c0ff1Tb4b4b4]Tb4b4b4)
Te6edf3to_delete Tff7b72= Te6edf3backupsTb4b4b4[Tb4b4b4: Tffa657lenTb4b4b4(Te6edf3backupsTb4b4b4) Tff7b72- Te6edf3max_countTb4b4b4]
Tff7b72for Te6edf3pathTb4b4b4, Te6edf3_ Tff7b72in Te6edf3to_deleteTb4b4b4:
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4:
Te6edf3osTff7b72.Td2a8ffremoveTb4b4b4(Te6edf3pathTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFailed to cleanup old backups: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_write_backup_baselineTb4b4b4(
Te6edf3storage_pathTb4b4b4,
Te6edf3current_statsTb4b4b4[Ta5d6ff"Ta5d6ffmessage_countTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3current_statsTb4b4b4[Ta5d6ff"Ta5d6fftotal_bytesTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3result
Tff7b72def Td2a8ffcreate_snapshotTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4, Te6edf3nameTb4b4b4: Tffa657strTb4b4b4)Tb4b4b4:
T8b949e"""Creates a named snapshot of the database."""
Te6edf3snapshot_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Ta5d6ff"Ta5d6ffsnapshotsTa5d6ff"Tb4b4b4)
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3snapshot_dirTb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
T8b949e# Ensure name is safe for filesystem
Te6edf3safe_name Tff7b72= Ta5d6ff"Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(
Tb4b4b4[Te6edf3c Tff7b72for Te6edf3c Tff7b72in Te6edf3name Tff7b72if Te6edf3cTff7b72.Td2a8ffisalnumTb4b4b4(Tb4b4b4) Tff7b72or Te6edf3c Tff7b72in Tb4b4b4(Ta5d6ff"Ta5d6ff Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff.Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff-Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff_Ta5d6ff"Tb4b4b4)Tb4b4b4]Tb4b4b4,
Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3safe_nameTb4b4b4:
Te6edf3safe_name Tff7b72= Ta5d6ff"Ta5d6ffunnamed_snapshotTa5d6ff"
Te6edf3snapshot_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3snapshot_dirTb4b4b4, Ta5d6fffTa5d6ff"Tffd700{Te6edf3safe_nameTffd700}Ta5d6ff.zipTa5d6ff"Tb4b4b4)
Tff7b72return Tff7b72selfTff7b72.Td2a8ff_backup_to_zipTb4b4b4(Te6edf3snapshot_pathTb4b4b4)
Tff7b72def Td2a8fflist_snapshotsTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3storage_pathTb4b4b4)Tb4b4b4:
T8b949e"""Lists all available snapshots."""
Te6edf3snapshot_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Ta5d6ff"Ta5d6ffsnapshotsTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3snapshot_dirTb4b4b4)Tb4b4b4:
Tff7b72return Tb4b4b4[Tb4b4b4]
Te6edf3snapshots Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3file Tff7b72in Te6edf3osTff7b72.Td2a8fflistdirTb4b4b4(Te6edf3snapshot_dirTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3fileTff7b72.Td2a8ffendswithTb4b4b4(Ta5d6ff"Ta5d6ff.zipTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3full_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3snapshot_dirTb4b4b4, Te6edf3fileTb4b4b4)
Te6edf3stats Tff7b72= Te6edf3osTff7b72.Td2a8ffstatTb4b4b4(Te6edf3full_pathTb4b4b4)
Te6edf3snapshotsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Te6edf3fileTb4b4b4,
Ta5d6ff"Ta5d6ffpathTa5d6ff"Tb4b4b4: Te6edf3full_pathTb4b4b4,
Ta5d6ff"Ta5d6ffsizeTa5d6ff"Tb4b4b4: Te6edf3statsTff7b72.Td2a8ffst_sizeTb4b4b4,
Ta5d6ff"Ta5d6ffcreated_atTa5d6ff"Tb4b4b4: Te6edf3datetimeTff7b72.Td2a8fffromtimestampTb4b4b4(
Te6edf3statsTff7b72.Td2a8ffst_mtimeTb4b4b4,
Te6edf3UTCTb4b4b4,
Tb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72return Tffa657sortedTb4b4b4(Te6edf3snapshotsTb4b4b4, Te6edf3keyTff7b72=Tff7b72lambda Te6edf3xTb4b4b4: Te6edf3xTb4b4b4[Ta5d6ff"Ta5d6ffcreated_atTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3reverseTff7b72=Tff7b72TrueTb4b4b4)
Tff7b72def Td2a8ffdelete_snapshot_or_backupTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3storage_pathTb4b4b4,
Te6edf3filenameTb4b4b4: Tffa657strTb4b4b4,
Te6edf3is_backupTb4b4b4: Tffa657bool Tff7b72= Tff7b72FalseTb4b4b4,
Tb4b4b4)Tb4b4b4:
T8b949e"""Deletes a database snapshot or auto-backup."""
Te6edf3base_dir Tff7b72= Ta5d6ff"Ta5d6ffdatabase-backupsTa5d6ff" Tff7b72if Te6edf3is_backup Tff7b72else Ta5d6ff"Ta5d6ffsnapshotsTa5d6ff"
Te6edf3file_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Te6edf3base_dirTb4b4b4, Te6edf3filenameTb4b4b4)
T8b949e# Basic security check to ensure we stay within the intended directory
Te6edf3abs_path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Te6edf3file_pathTb4b4b4)
Te6edf3abs_base Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3storage_pathTb4b4b4, Te6edf3base_dirTb4b4b4)Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3abs_pathTff7b72.Td2a8ffstartswithTb4b4b4(Te6edf3abs_baseTb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffInvalid pathTa5d6ff"
Tff7b72raise Tf85149ValueErrorTb4b4b4(Te6edf3msgTb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3abs_pathTb4b4b4)Tb4b4b4:
Te6edf3osTff7b72.Td2a8ffremoveTb4b4b4(Te6edf3abs_pathTb4b4b4)
Tff7b72return Tff7b72True
Tff7b72return Tff7b72False
Tf0883e@staticmethod
Tff7b72def Td2a8ff_looks_like_sqliteTb4b4b4(Te6edf3pathTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657boolTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72with Tffa657openTb4b4b4(Te6edf3pathTb4b4b4, Ta5d6ff"Ta5d6ffrbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3handleTb4b4b4:
Tff7b72return Te6edf3handleTff7b72.Td2a8ffreadTb4b4b4(T79c0ff16Tb4b4b4) Tff7b72== Ta5d6ffbTa5d6ff"Ta5d6ffSQLite format 3Tffea00\x00Ta5d6ff"
Tff7b72except Tf85149OSErrorTb4b4b4:
Tff7b72return Tff7b72False
Tff7b72def Td2a8ffrestore_databaseTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3backup_pathTb4b4b4: Tffa657strTb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3backup_pathTb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffBackup not found at Tffd700{Te6edf3backup_pathTffd700}Ta5d6ff"
Tff7b72raise Tf85149FileNotFoundErrorTb4b4b4(Te6edf3msgTb4b4b4)
Te6edf3paths Tff7b72= Tff7b72selfTff7b72.Td2a8ff_database_pathsTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ff_checkpoint_and_closeTb4b4b4(Tb4b4b4)
Tff7b72for Te6edf3p Tff7b72in Te6edf3pathsTff7b72.Td2a8ffvaluesTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexistsTb4b4b4(Te6edf3pTb4b4b4)Tb4b4b4:
Te6edf3osTff7b72.Td2a8ffremoveTb4b4b4(Te6edf3pTb4b4b4)
Tff7b72if Te6edf3zipfileTff7b72.Td2a8ffis_zipfileTb4b4b4(Te6edf3backup_pathTb4b4b4)Tb4b4b4:
Te6edf3target_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72with Te6edf3zipfileTff7b72.Td2a8ffZipFileTb4b4b4(Te6edf3backup_pathTb4b4b4, Ta5d6ff"Ta5d6ffrTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3zfTb4b4b4:
Tff7b72for Te6edf3member Tff7b72in Te6edf3zfTff7b72.Td2a8ffnamelistTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3memberTff7b72.Td2a8ffendswithTb4b4b4(Ta5d6ff"Ta5d6ff/Ta5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72continue
Tff7b72selfTff7b72.Td2a8ff_safe_zip_extract_memberTb4b4b4(Te6edf3zfTb4b4b4, Te6edf3memberTb4b4b4, Te6edf3target_dirTb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3shutilTff7b72.Td2a8ffcopy2Tb4b4b4(Te6edf3backup_pathTb4b4b4, Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72if Tff7b72not Tff7b72selfTff7b72.Td2a8ff_looks_like_sqliteTb4b4b4(Te6edf3pathsTb4b4b4[Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4]Tb4b4b4)Tb4b4b4:
Tff7b72raise Te6edf3DatabaseRestoreErrorTb4b4b4(Ta5d6ff"Ta5d6ffRestored file is not a valid SQLite databaseTa5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffinitializeTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3excTb4b4b4:
Tff7b72raise Te6edf3DatabaseRestoreErrorTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffRestored files from backup but database failed to open: Tffd700{Te6edf3excTffd700!s}Ta5d6ff"Tb4b4b4,
Tb4b4b4) Tff7b72from T7ee787exc
Tff7b72selfTff7b72.Td2a8ff_tune_sqlite_pragmasTb4b4b4(Tb4b4b4)
Te6edf3integrity_rows Tff7b72= Tff7b72selfTff7b72.Td2a8ffproviderTff7b72.Td2a8ffintegrity_checkTb4b4b4(Tb4b4b4)
Te6edf3integrity Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3row Tff7b72in Te6edf3integrity_rows Tff7b72or Tb4b4b4[Tb4b4b4]Tb4b4b4:
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3rowTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3integrityTff7b72.Td2a8ffappendTb4b4b4(Tffa657nextTb4b4b4(Tffa657iterTb4b4b4(Te6edf3rowTff7b72.Td2a8ffvaluesTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3integrityTff7b72.Td2a8ffappendTb4b4b4(Te6edf3rowTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4)
Tff7b72if Te6edf3integrity Tff7b72and Te6edf3integrityTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72!= Ta5d6ff"Ta5d6ffokTa5d6ff"Tb4b4b4:
Tff7b72raise Te6edf3DatabaseRestoreErrorTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffRestored backup failed integrity check: Tffd700{Te6edf3integrityTb4b4b4[T79c0ff0Tb4b4b4]Tffd700!s}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffrestored_fromTa5d6ff"Tb4b4b4: Te6edf3backup_pathTb4b4b4,
Ta5d6ff"Ta5d6ffintegrity_checkTa5d6ff"Tb4b4b4: Te6edf3integrity_rowsTb4b4b4,
Ta5d6ff"Ta5d6ffhealthTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffget_database_health_snapshotTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────